home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue25 / construc / CGIAPP.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1997-06-24  |  16.5 KB  |  590 lines

  1.  
  2. {*******************************************************}
  3. {                                                       }
  4. {       Delphi Visual Component Library                 }
  5. {       CGI/WinCGI Web server application components    }
  6. {                                                       }
  7. {       Copyright (c) 1997 Borland International        }
  8. {                                                       }
  9. {*******************************************************}
  10.  
  11. unit CGIApp;
  12.  
  13. interface
  14.  
  15. uses Windows, Classes, HTTPApp, IniFiles;
  16.  
  17. type
  18.   TCGIRequest = class(TWebRequest)
  19.   private
  20.     FContent: string;
  21.   protected
  22.     function GetStringVariable(Index: Integer): string; override;
  23.     function GetDateVariable(Index: Integer): TDateTime; override;
  24.     function GetIntegerVariable(Index: Integer): Integer; override;
  25.   public
  26.     constructor Create;
  27.     function GetFieldByName(const Name: string): string; override;
  28.     function ReadClient(var Buffer; Count: Integer): Integer; override;
  29.     function ReadString(Count: Integer): string; override;
  30.     function TranslateURI(const URI: string): string; override;
  31.     function WriteClient(var Buffer; Count: Integer): Integer; override;
  32.     function WriteString(const AString: string): Boolean; override;
  33.   end;
  34.  
  35.   TCGIResponse = class(TWebResponse)
  36.   private
  37.     FStatusCode: Integer;
  38.     FStringVariables: array[0..MAX_STRINGS - 1] of string;
  39.     FIntegerVariables: array[0..MAX_INTEGERS - 1] of Integer;
  40.     FDateVariables: array[0..MAX_DATETIMES - 1] of TDateTime;
  41.     FContent: string;
  42.     FSent: Boolean;
  43.   protected
  44.     function GetContent: string; override;
  45.     function GetDateVariable(Index: Integer): TDateTime; override;
  46.     function GetIntegerVariable(Index: Integer): Integer; override;
  47.     function GetLogMessage: string; override;
  48.     function GetStatusCode: Integer; override;
  49.     function GetStringVariable(Index: Integer): string; override;
  50.     function Sent: Boolean; override;
  51.     procedure SetContent(const Value: string); override;
  52.     procedure SetDateVariable(Index: Integer; const Value: TDateTime); override;
  53.     procedure SetIntegerVariable(Index: Integer; Value: Integer); override;
  54.     procedure SetLogMessage(const Value: string); override;
  55.     procedure SetStatusCode(Value: Integer); override;
  56.     procedure SetStringVariable(Index: Integer; const Value: string); override;
  57.   public
  58.     constructor Create(HTTPRequest: TWebRequest);
  59.     procedure SendResponse; override;
  60.     procedure SendRedirect(const URI: string); override;
  61.     procedure SendStream(AStream: TStream); override;
  62.   end;
  63.  
  64.   TWinCGIRequest = class(TCGIRequest)
  65.   private
  66.     FIniFile: TIniFile;
  67.     FClientData, FServerData: TFileStream;
  68.   protected
  69.     function GetStringVariable(Index: Integer): string; override;
  70.   public
  71.     constructor Create(IniFileName, ContentFile, OutputFile: string);
  72.     destructor Destroy; override;
  73.     function GetFieldByName(const Name: string): string; override;
  74.     function ReadClient(var Buffer; Count: Integer): Integer; override;
  75.     function ReadString(Count: Integer): string; override;
  76.     function TranslateURI(const URI: string): string; override;
  77.     function WriteClient(var Buffer; Count: Integer): Integer; override;
  78.     function WriteString(const AString: string): Boolean; override;
  79.   end;
  80.  
  81.   TWinCGIResponse = class(TCGIResponse);
  82.  
  83.   TCGIApplication = class(TWebApplication)
  84.   private
  85.     FOutputFileName: string;
  86.     function NewRequest: TCGIRequest;
  87.     function NewResponse(CGIRequest: TCGIRequest): TCGIResponse;
  88.   public
  89.     procedure Run; override;
  90.   end;
  91.  
  92. implementation
  93.  
  94. uses SysUtils, WebConst, Dialogs;
  95.  
  96. const
  97.   CGIServerVariables: array[0..28] of string = (
  98.     'REQUEST_METHOD',
  99.     'SERVER_PROTOCOL',
  100.     'URL',
  101.     'QUERY_STRING',
  102.     'PATH_INFO',
  103.     'PATH_TRANSLATED',
  104.     'HTTP_CACHE_CONTROL',
  105.     'HTTP_DATE',
  106.     'HTTP_ACCEPT',
  107.     'HTTP_FROM',
  108.     'HTTP_HOST',
  109.     'HTTP_IF_MODIFIED_SINCE',
  110.     'HTTP_REFERER',
  111.     'HTTP_USER_AGENT',
  112.     'HTTP_CONTENT_ENCODING',
  113.     'HTTP_CONTENT_TYPE',
  114.     'HTTP_CONTENT_LENGTH',
  115.     'HTTP_CONTENT_VERSION',
  116.     'HTTP_DERIVED_FROM',
  117.     'HTTP_EXPIRES',
  118.     'HTTP_TITLE',
  119.     'REMOTE_ADDR',
  120.     'REMOTE_HOST',
  121.     'SCRIPT_NAME',
  122.     'SERVER_PORT',
  123.     '',
  124.     'HTTP_CONNECTION',
  125.     'HTTP_COOKIE',
  126.     'HTTP_AUTHORIZATION');
  127.  
  128. { TCGIRequest }
  129.  
  130. constructor TCGIRequest.Create;
  131. begin
  132.   inherited Create;
  133.   FContent := ReadString(ContentLength);
  134. end;
  135.  
  136. function TCGIRequest.GetFieldByName(const Name: string): string;
  137. var
  138.   Buffer: array[0..4095] of Char;
  139.  
  140.   function StripHTTP(const Name: string): string;
  141.   begin
  142.     if Pos('HTTP_', Name) = 1 then
  143.       Result := Copy(Name, 6, MaxInt)
  144.     else Result := Name;
  145.   end;
  146.  
  147. begin
  148.   SetString(Result, Buffer, GetEnvironmentVariable(PChar(Name), Buffer, SizeOf(Buffer)));
  149.   if Result = '' then
  150.     SetString(Result, Buffer, GetEnvironmentVariable(PChar(StripHTTP(Name)), Buffer, SizeOf(Buffer)));
  151. end;
  152.  
  153. function TCGIRequest.GetStringVariable(Index: Integer): string;
  154. begin
  155.   if Index = 25 then
  156.     Result := FContent
  157.   else Result := GetFieldByName(CGIServerVariables[Index]);
  158. end;
  159.  
  160. function TCGIRequest.GetDateVariable(Index: Integer): TDateTime;
  161. var
  162.   Value: string;
  163. begin
  164.   Value := GetStringVariable(Index);
  165.   if Value <> '' then
  166.     Result := ParseDate(Value)
  167.   else Result := -1;
  168. end;
  169.  
  170. function TCGIRequest.GetIntegerVariable(Index: Integer): Integer;
  171. var
  172.   Value: string;
  173. begin
  174.   Value := GetStringVariable(Index);
  175.   Result := StrToIntDef(Value, -1)
  176. end;
  177.  
  178. function TCGIRequest.ReadClient(var Buffer; Count: Integer): Integer;
  179. begin
  180.   Result := FileRead(TTextRec(Input).Handle, Buffer, Count);
  181. end;
  182.  
  183. function TCGIRequest.ReadString(Count: Integer): string;
  184. begin
  185.   SetLength(Result, Count);
  186.   if Count > 0 then
  187.     FileRead(TTextRec(Input).Handle, Pointer(Result)^, Count);
  188. end;
  189.  
  190. function TCGIRequest.TranslateURI(const URI: string): string;
  191. begin
  192. end;
  193.  
  194. function TCGIRequest.WriteClient(var Buffer; Count: Integer): Integer;
  195. begin
  196.   Result := FileWrite(TTextRec(Output).Handle, Buffer, Count);
  197. end;
  198.  
  199. function TCGIRequest.WriteString(const AString: string): Boolean;
  200. begin
  201.   if AString <> '' then
  202.     Result := FileWrite(TTextRec(Output).Handle, Pointer(AString)^, Length(AString)) = Length(AString)
  203.   else Result := False;
  204. end;
  205.  
  206. { TCGIResponse }
  207.  
  208. constructor TCGIResponse.Create(HTTPRequest: TWebRequest);
  209. begin
  210.   inherited Create(HTTPRequest);
  211.   if FHTTPRequest.ProtocolVersion = '' then
  212.     Version := '1.0';
  213.   StatusCode := 200;
  214.   LastModified := -1;
  215.   Expires := -1;
  216.   Date := -1;
  217.   ContentType := 'text/html';
  218. end;
  219.  
  220. function TCGIResponse.GetContent: string;
  221. begin
  222.   Result := FContent;
  223. end;
  224.  
  225. function TCGIResponse.GetDateVariable(Index: Integer): TDateTime;
  226. begin
  227.   if (Index >= 0) and (Index < 3) then
  228.     Result := FDateVariables[Index]
  229.   else Result := -1;
  230. end;
  231.  
  232. function TCGIResponse.GetIntegerVariable(Index: Integer): Integer;
  233. begin
  234.   if (Index >= 0) and (Index < 2) then
  235.     Result := FIntegerVariables[Index]
  236.   else Result := -1;
  237. end;
  238.  
  239. function TCGIResponse.GetLogMessage: string;
  240. begin
  241. //  Result := TCGIRequest(HTTPRequest).ECB.lpszLogData;
  242. end;
  243.  
  244. function TCGIResponse.GetStatusCode: Integer;
  245. begin
  246.   Result := FStatusCode;
  247. end;
  248.  
  249. function TCGIResponse.GetStringVariable(Index: Integer): string;
  250. begin
  251.   if (Index >= 0) and (Index < 12) then
  252.     Result := FStringVariables[Index];
  253. end;
  254.  
  255. function TCGIResponse.Sent: Boolean;
  256. begin
  257.   Result := FSent;
  258. end;
  259.  
  260. procedure TCGIResponse.SetContent(const Value: string);
  261. begin
  262.   FContent := Value;
  263.   ContentLength := Length(FContent);
  264. end;
  265.  
  266. procedure TCGIResponse.SetDateVariable(Index: Integer; const Value: TDateTime);
  267. begin
  268.   if (Index >= Low(FDateVariables)) and (Index <= High(FDateVariables)) then
  269.     if Value <> FDateVariables[Index] then
  270.       FDateVariables[Index] := Value;
  271. end;
  272.  
  273. procedure TCGIResponse.SetIntegerVariable(Index: Integer; Value: Integer);
  274. begin
  275.   if (Index >= Low(FIntegerVariables)) and (Index <= High(FIntegerVariables)) then
  276.     if Value <> FDateVariables[Index] then
  277.       FIntegerVariables[Index] := Value;
  278. end;
  279.  
  280. procedure TCGIResponse.SetLogMessage(const Value: string);
  281. begin
  282. //  StrPLCopy(TCGIRequest(HTTPRequest).ECB.lpszLogData, Value, HSE_LOG_BUFFER_LEN);
  283. end;
  284.  
  285. procedure TCGIResponse.SetStatusCode(Value: Integer);
  286. begin
  287.   if FStatusCode <> Value then
  288.   begin
  289.     FStatusCode := Value;
  290.     ReasonString := StatusString(Value);
  291.   end;
  292. end;
  293.  
  294. procedure TCGIResponse.SetStringVariable(Index: Integer; const Value: string);
  295. begin
  296.   if (Index >= Low(FStringVariables)) and (Index <= High(FStringVariables)) then
  297.     FStringVariables[Index] := Value;
  298. end;
  299.  
  300. procedure TCGIResponse.SendResponse;
  301. var
  302.   StatusString: string;
  303.   Headers: string;
  304.   I: Integer;
  305.  
  306.   procedure AddHeaderItem(const Item, FormatStr: string);
  307.   begin
  308.     if Item <> '' then
  309.       Headers := Headers + Format(FormatStr, [Item]);
  310.   end;
  311.  
  312. begin
  313.   if HTTPRequest.ProtocolVersion <> '' then
  314.   begin
  315.     if (ReasonString <> '') and (StatusCode > 0) then
  316.       StatusString := Format('%d %s', [StatusCode, ReasonString])
  317.     else StatusString := '200 OK';
  318.     AddHeaderItem(StatusString, 'Status: %s'#13#10);
  319.     AddHeaderItem(Allow, 'Allow: %s'#13#10);
  320.     for I := 0 to Cookies.Count - 1 do
  321.       AddHeaderItem(Cookies[I].HeaderValue, 'Set-Cookie: %s'#13#10);
  322.     AddHeaderItem(DerivedFrom, 'Derived-From: %s'#13#10);
  323.     if Expires > 0 then
  324.       Headers := Headers +
  325.         FormatDateTime('"Expires: "' + DateFormat + ' "GMT"'#13#10, Expires);
  326.     if LastModified > 0 then
  327.       Headers := Headers +
  328.         FormatDateTime('"Last-Modified: "' + DateFormat + ' "GMT"'#13#10, LastModified);
  329.     AddHeaderItem(Title, 'Title: %s'#13#10);
  330.     AddHeaderItem(WWWAuthenticate, 'WWW-Authenticate: %s'#13#10);
  331.     AddCustomHeaders(Headers);
  332.     AddHeaderItem(ContentVersion, 'Content-Version: %s'#13#10);
  333.     AddHeaderItem(ContentEncoding, 'Content-Encoding: %s'#13#10);
  334.     AddHeaderItem(ContentType, 'Content-Type: %s'#13#10);
  335.     if (Content <> '') or (ContentStream <> nil) then
  336.       AddHeaderItem(IntToStr(ContentLength), 'Content-Length: %s'#13#10);
  337.     Headers := Headers + 'Content:'#13#10#13#10;
  338.     HTTPRequest.WriteString(Headers);
  339.   end;
  340.   if ContentStream = nil then
  341.     HTTPRequest.WriteString(Content)
  342.   else if ContentStream <> nil then
  343.   begin
  344.     SendStream(ContentStream);
  345.     ContentStream := nil; // Drop the stream
  346.   end;
  347.   FSent := True;
  348. end;
  349.  
  350. procedure TCGIResponse.SendRedirect(const URI: string);
  351. begin
  352.   HTTPRequest.WriteString(Format('Location: %s', [URI]));
  353.   FSent := True;
  354. end;
  355.  
  356. procedure TCGIResponse.SendStream(AStream: TStream);
  357. var
  358.   Buffer: array[0..8191] of Byte;
  359.   BytesToSend: Integer;
  360. begin
  361.   while AStream.Position < AStream.Size do
  362.   begin
  363.     BytesToSend := AStream.Read(Buffer, SizeOf(Buffer));
  364.     FHTTPRequest.WriteClient(Buffer, BytesToSend);
  365.   end;
  366. end;
  367.  
  368. const
  369.   WinCGIServerVariables: array[0..28] of string = (
  370.     'Request Method',
  371.     'Request Protocol',
  372.     'Url',
  373.     'Query String',
  374.     'Logical Path',
  375.     'Physical Path',
  376.     'Cache Control',
  377.     'Date',
  378.     'Accept',
  379.     'From',
  380.     'Host',
  381.     'If-Modified-Since',
  382.     'Referer',
  383.     'User-Agent',
  384.     'Content-Encoding',
  385.     'Content Type',
  386.     'Content Length',
  387.     'Content Version',
  388.     'Derived-From',
  389.     'Expires',
  390.     'Title',
  391.     'Remote Address',
  392.     'Remote Host',
  393.     'Executable Path',
  394.     'Server Port',
  395.     '',
  396.     'Connection',
  397.     'Cookie',
  398.     'Authorization');
  399.  
  400. { TWinCGIRequest }
  401.  
  402. constructor TWinCGIRequest.Create(IniFileName, ContentFile, OutputFile: string);
  403. begin
  404. { ShowMessage(IniFileName); }
  405.   FIniFile := TIniFile.Create(IniFileName);
  406. { ShowMessage(ContentFile); }
  407.   if ContentFile = '' then
  408.     ContentFile := FIniFile.ReadString('System', 'Content File', '');
  409. { ShowMessage(ContentFile); }
  410. { ShowMessage(OutputFile); }
  411.   if OutputFile = '' then
  412.     OutputFile := FIniFile.ReadString('System', 'Output File', '');
  413. { ShowMessage(OutputFile); }
  414.   FClientData := TFileStream.Create(ContentFile, fmOpenRead or fmShareDenyNone);
  415.   FServerData := TFileStream.Create(OutputFile, {fmCreate} fmOpenWrite or fmShareDenyNone);
  416.   inherited Create;
  417. end;
  418.  
  419. destructor TWinCGIRequest.Destroy;
  420. begin
  421.   FIniFile.Free;
  422.   FClientData.Free;
  423.   FServerData.Free;
  424.   inherited Destroy;
  425. end;
  426.  
  427. function TWinCGIRequest.GetFieldByName(const Name: string): string;
  428. begin
  429.   Result := FIniFile.ReadString('Extra Headers', Name, '');
  430. end;
  431.  
  432. function TWinCGIRequest.GetStringVariable(Index: Integer): string;
  433.  
  434.   function AcceptSection: string;
  435.   var
  436.     Section: TStringList;
  437.     I: Integer;
  438.   begin
  439.     Section := TStringList.Create;
  440.     try
  441.       FIniFile.ReadSection('Accept', Section);
  442.       Result := '';
  443.       for I := 0 to Section.Count - 1 do
  444.         Result := Result + Section[I] + ',';
  445.       if Result <> '' then SetLength(Result, Length(Result) - 1);
  446.     finally
  447.       Section.Free;
  448.     end;
  449.   end;
  450.  
  451. begin
  452.   case Index of
  453.     0..1,3..5,15..16,
  454.     21..24, 26..28:
  455.       Result := FIniFile.ReadString('CGI', WinCGIServerVariables[Index], '');
  456.     25: Result := FContent;
  457.     8: Result := AcceptSection;
  458.   else
  459.     if (Index >= Low(WinCGIServerVariables)) and (Index <= High(WinCGIServerVariables)) then
  460.       Result := GetFieldByName(WinCGIServerVariables[Index])
  461.     else Result := '';
  462.   end;
  463. end;
  464.  
  465. function TWinCGIRequest.ReadClient(var Buffer; Count: Integer): Integer;
  466. begin
  467.   Result := FClientData.Read(Buffer, Count);
  468. end;
  469.  
  470. function TWinCGIRequest.ReadString(Count: Integer): string;
  471. begin
  472.   SetLength(Result, Count);
  473.   if Count > 0 then
  474.     FClientData.Read(Pointer(Result)^, Count);
  475. end;
  476.  
  477. function TWinCGIRequest.TranslateURI(const URI: string): string;
  478. begin
  479. end;
  480.  
  481. function TWinCGIRequest.WriteClient(var Buffer; Count: Integer): Integer;
  482. begin
  483.   Result := FServerData.Write(Buffer, Count);
  484. end;
  485.  
  486. function TWinCGIRequest.WriteString(const AString: string): Boolean;
  487. begin
  488.   if AString <> '' then
  489.     Result := FServerData.Write(Pointer(AString)^, Length(AString)) = Length(AString)
  490.   else Result := False;
  491. end;
  492.  
  493. { TCGIApplication }
  494.  
  495. procedure HandleServerException(E: Exception; const OutputFile: string);
  496. var
  497.   ResultText, ResultHeaders: string;
  498.   OutFile: TStream;
  499. begin
  500.   ResultText := Format(sInternalServerError, [E.ClassName, E.Message]);
  501.   ResultHeaders := Format(
  502.     'Status: 500 %s'#13#10+               //Not resourced
  503.     'Content-Type: text/html'#13#10 +     //Not resourced
  504.     'Content-Length: %d'#13#10 +          //Not resourced
  505.     'Content:'#13#10#13#10, [E.Message, Length(ResultText)]); //Not resourced
  506.   if IsConsole then
  507.   begin
  508.     FileWrite(TTextRec(Output).Handle, Pointer(ResultHeaders)^, Length(ResultHeaders));
  509.     FileWrite(TTextRec(Output).Handle, Pointer(ResultText)^, Length(ResultText));
  510.   end else
  511.   begin
  512.     OutFile := TFileStream.Create(OutputFile, {fmCreate} fmOpenWrite or fmShareDenyNone);
  513.     try
  514.       OutFile.Write(Pointer(ResultHeaders)^, Length(ResultHeaders));
  515.       OutFile.Write(Pointer(ResultText)^, Length(ResultText));
  516.     finally
  517.       OutFile.Free;
  518.     end;
  519.   end;
  520. end;
  521.  
  522. function TCGIApplication.NewRequest: TCGIRequest;
  523. var
  524.   Buffer: array[0..MAX_PATH] of Char;
  525. begin
  526.   if IsConsole then
  527.     Result := TCGIRequest.Create
  528.   else
  529.   begin
  530.     Result := TWinCGIRequest.Create(ParamStr(1), ParamStr(2), ParamStr(3));
  531.     FOutputFileName := ParamStr(3);
  532.     if FOutputFileName = '' then
  533.       SetString(FOutputFileName, Buffer, GetPrivateProfileString('System',
  534.         'Output File', '', Buffer, SizeOf(Buffer), PChar(ParamStr(1))));
  535.   end;
  536. end;
  537.  
  538. function TCGIApplication.NewResponse(CGIRequest: TCGIRequest): TCGIResponse;
  539. begin
  540.   if IsConsole then
  541.     Result := TCGIResponse.Create(CGIRequest)
  542.   else Result := TWinCGIResponse.Create(CGIRequest);
  543. end;
  544.  
  545. procedure TCGIApplication.Run;
  546. var
  547.   HTTPRequest: TCGIRequest;
  548.   HTTPResponse: TCGIResponse;
  549. begin
  550.   inherited Run;
  551.   if IsConsole then
  552.   begin
  553.     Rewrite(Output);
  554.     Reset(Input);
  555.   end;
  556.   try
  557.     HTTPRequest := NewRequest;
  558.     try
  559.       HTTPResponse := NewResponse(HTTPRequest);
  560.       try
  561.         HandleRequest(HTTPRequest, HTTPResponse);
  562.       finally
  563.         HTTPResponse.Free;
  564.       end;
  565.     finally
  566.       HTTPRequest.Free;
  567.     end;
  568.   except
  569.     HandleServerException(Exception(ExceptObject), FOutputFileName);
  570.   end;
  571. end;
  572.  
  573. procedure InitApplication;
  574. begin
  575.   Application := TCGIApplication.Create(nil);
  576. end;
  577.  
  578. procedure DoneApplication;
  579. begin
  580.   Application.Free;
  581.   Application := nil;
  582. end;
  583.  
  584. initialization
  585.   InitApplication;
  586. finalization
  587.   DoneApplication;
  588. end.
  589.  
  590.